/** 
 * PartnershipAnalyzer.java Created by iweerarathna on Sep 2, 2011.
 * 
 * Copyright (c) 2011 Isuru Madushanka Weerarathna. All Rights Reserved.<br><br>. 
 * 
 */
package org.uim.cricket.analyzer.analyze.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.uim.cricket.analyzer.analyze.Alogger;
import org.uim.cricket.analyzer.analyze.AnalyzerCommonFunctions;
import org.uim.cricket.analyzer.analyze.CricketMatchAnalyzable;
import org.uim.cricket.entities.MatchPlayer;
import org.uim.cricket.entities.aggr.SingleInning;
import org.uim.cricket.entities.batting.BattingInning;
import org.uim.cricket.entities.batting.OutTypes;
import org.uim.cricket.entities.match.CricketMatch;
import org.uim.cricket.entities.partnerships.FallOfWicket;
import org.uim.cricket.entities.partnerships.Partnership;
import org.uim.cricket.util.CricketFunctions;

/**
 * Creates a set of partnership objects from given fall-of-wicket objects.
 * 
 * @version 1.0
 * @author iweerarathna
 */
public class PartnershipAnalyzer implements CricketMatchAnalyzable {
	
    private CricketFunctions cf = null;
    private AnalyzerCommonFunctions comf = new AnalyzerCommonFunctions();
    
    private String lastPOver = "0";
    
    public PartnershipAnalyzer() {
    	cf = comf.getCricketFunctions();
    }
    
    @Override
	public void doAnalyze(CricketMatch match, Map<String, List<MatchPlayer>> players) throws Exception {
    	if (match == null) return;
    	
    	SingleInning[] innings = match.getInnings();
		if (innings == null) return;
		
		for (int i=0;i<innings.length;i++) {
			SingleInning inn = innings[i];
			if (inn==null) continue;
			
			Alogger.debug("******  Analyzing partnerships at Inning #" + (i+1) + "... ");
			List<Partnership> pshipList = this.makePartnerships(inn.getFallWickets(), inn);
			if (pshipList != null) {
				Partnership[] pships = new Partnership[pshipList.size()];
				pshipList.toArray(pships);
				inn.setPartnerships(pships);
			} else {
				Alogger.warn("No partnerships are found!");
			}
			Alogger.debug("        Completed... ");
		}
	}
    
    private List<Partnership> makePartnerships(List<FallOfWicket> wickets, SingleInning inn) {
        if (inn == null) return null;
        if (inn.getBatInnings() == null) return null;
        
        Alogger.printSubHeader("Making partnerships...");
    	List<Partnership> pships = this.makeBasicPShips(wickets, inn);
    	Alogger.debug("  " + pships.size() + " partnerships made.");
    	Alogger.debug("Completed.");
    	
    	Alogger.debug(" ----- sets runs and balls for each partnership...");
    	lastPOver = "0";
    	for (int i=0; i<pships.size(); i++) {
    		this.setPShipBallsRuns(pships.get(i), inn);
    	}
    	Alogger.debug(" ----- finished. ");
    	
    	Alogger.debug(" ----- puts all batting innings to the list...");
    	Stack<BattingInning> batsmen = this.makeBatsmenList(inn.getBatInnings(), inn);
    	Alogger.info("      " + batsmen.size() + " batsmen added.");
    	Alogger.debug(" ----- finished. ");
    	
    	if (batsmen.size() < 2) {
    		Alogger.warn("Number of batsmen in the batting line up is less than 2 (<2)!!");
    		Alogger.warn("Analysis is aborted!");
    		return pships;
    	}
    	
    	List<BattingInning> poolBatsman = new ArrayList<BattingInning>();
    	List<BattingInning> retiredBatsman = new ArrayList<BattingInning>();
    	Map<Integer, BattingInning> retiredComeBacks = new HashMap<Integer, BattingInning>();
    	
    	poolBatsman.add(batsmen.pop());
    	poolBatsman.add(batsmen.pop());
    	
    	for (int i=0; i<pships.size(); i++) {
    		
    		Partnership p = pships.get(i);
    		
    		p.setStartPlayer(poolBatsman.get(0));
    		p.setArrivedPlayer(poolBatsman.get(1));
    		
    		if (p.getEndWicket() != null) {
    			
    			int pindex = comf.containInBatList(poolBatsman, p.getEndWicket().getOutPlayer());
    			
    			if (pindex < 0) {
    				pindex = comf.containInBatList(retiredBatsman, p.getEndWicket().getOutPlayer());
    				if (pindex < 0) {
    					Alogger.warn("  batsman is not found!");
    					Alogger.warn("     [outplayer: " + p.getEndWicket().getOutPlayer().getTitleName() + "]"); 
    					return pships;
    				}
    				
    				p.setOutPlayer(retiredBatsman.get(pindex));
    				retiredBatsman.remove(pindex);
    				
    			} else {
    				
    				if (p.getEndWicket().isRetiredHurt()) {
    					if (p.getEndWicket().getRetiredComeBackWicket() > 0) {
    						Alogger.debug("    -> adding retired hurt comeback for " + p.getEndWicket().getOutPlayer().getTitleName() +
    								" in wicket #" + p.getWicketNo());
    						retiredComeBacks.put(p.getEndWicket().getRetiredComeBackWicket(), poolBatsman.get(pindex));
    					}
    					retiredBatsman.add(poolBatsman.get(pindex));
    					p.setRetired(true);
    				}
    				p.setOutPlayer(poolBatsman.get(pindex));
    				poolBatsman.remove(pindex);
    				
    				if (retiredComeBacks.containsKey(p.getWicketNo())) {
    					Alogger.debug("    -> popping retired hurt comeback for wicket #" + p.getWicketNo());
						poolBatsman.add(retiredComeBacks.get(p.getWicketNo()));
						retiredComeBacks.remove(p.getWicketNo());
					} else {
						if (batsmen.size() > 0) {
	    					poolBatsman.add(batsmen.pop());
	    				} else {
	    					if (retiredBatsman.size() > 0) {
	    						poolBatsman.add(retiredBatsman.get(0));
	    						retiredBatsman.remove(0);
	    					} else {
	    						break;
	    					}
	    				}
					}
    				
    				
    			}
    			
    		} else {
    		
    			break;
    			
    		}
    		
    	}
    	
    	return pships;
    }

    /**
     * Creates simple partnership objects - only by adding fall of wicket objects to
     * a partnership. 
     * 
     * @param wickets List of all fall of wickets.
     * @param inn The inning which all fall of wickets are belonging.
     * @return A list of partnerships included corresponding fall of wickets with in them.
     */
    private List<Partnership> makeBasicPShips(List<FallOfWicket> wickets, SingleInning inn) {
    	List<Partnership> pshiplist = new ArrayList<Partnership>();
        
    	if (wickets == null || wickets.size() == 0) return pshiplist;
        
    	FallOfWicket prevWkt = null;
    	FallOfWicket nowWkt = null;
    	Partnership pship = null;
    	
        for (int i=0; i<wickets.size(); i++) {
        	
        	nowWkt = wickets.get(i);
        	if (pship == null) pship = new Partnership();
        		
    		pship.setBeginWicket(prevWkt);
    		pship.setEndWicket(nowWkt);
    		
    		pshiplist.add(pship);
    		
    		prevWkt = nowWkt;
    		pship = null;

        }
    	
        if (inn.getTotals() != null) {
        	
        	if (inn.getTotals().getWickets() < 10) {
        		
        		if (pship == null) {
        			
        			Partnership last = new Partnership();
            		last.setBeginWicket(prevWkt);
            		last.setEndWicket(null);
            		last.setWicketNo(inn.getTotals().getWickets()+1);
            		last.setNotOut(true);
            		
            		pshiplist.add(last);
            		
        		} 
        		
        	}
        	
        }
        
        return pshiplist;
    }
    
    /**
     * Creates a stack of batting innings by using a list of batting inning. The purpose of 
     * creating a stack is to identify the correct order of batsman involvements in to the
     * partnerships. On the top of stack there is the first batsman, and then second bastman,
     * and so on.
     * 
     * @param battings List of batting innings.
     * @param inn The inning which belongs those batting innings.
     * @return A stack of batting innings in order of batting position.
     */
    private Stack<BattingInning> makeBatsmenList(List<BattingInning> battings, SingleInning inn) {
    	
    	Stack<BattingInning> batsmen = new Stack<BattingInning>();
    	
    	for (int i=battings.size()-1;i>=0;i--) {
    		BattingInning tmp = battings.get(i);
    		if (tmp == null || tmp.getOutDetails() == null) continue;
    		if (tmp.getOutDetails().getOutType() == OutTypes.DID_NOT_BAT) continue;
    		
    		batsmen.add(tmp);
    	}
    	
    	return batsmen;
    }
    
    /**
     * <p>Sets partnership runs and balls by examining its fall of wicket components.
     * If <code>beginWicket</code> is null that means this partnership is the first 
     * partnership. If <code>endWicket</code> is null, that means it is the last 
     * partnership. Otherwise it is considered as partnership in the middle of innings.</p>
     * 
     * 	<p>Runs are calculated by subtracting end runs in <code>endWicket</code> and
     * start runs in <code>beginWicket</code>. </p>
     * 
     * @param pship The partnership is to be checked.
     * @param inn The inning the partnership belongs.
     */
    private void setPShipBallsRuns(Partnership pship, SingleInning inn) {
    	FallOfWicket prevWkt = pship.getBeginWicket();
    	FallOfWicket nowWkt = pship.getEndWicket();
    	
    	String startOver = "0";
        int startRuns = 0;
        int swkt = 0;
        int ewkt = 0;
        if (prevWkt != null) {
        	startRuns = prevWkt.getFallOfRuns();
        	swkt = prevWkt.getWicket();
        	if (prevWkt.getFallOfOver() != null) {
        		startOver = prevWkt.getFallOfOver().trim();
        	} else {
        		startOver = lastPOver.trim();
        	}
        }
        
        int endRuns = 0;
        String endOver = "0";
        if (nowWkt != null) {
        	ewkt = nowWkt.getWicket();
        	endRuns = nowWkt.getFallOfRuns();
        	if (nowWkt.getFallOfOver() != null) {
        		endOver = nowWkt.getFallOfOver().trim();
        		lastPOver = endOver;
        	}
        } else {
        	
        	if (inn.getTotals() != null) {
        		if (inn.getTotals().getOvers() != null) {
        			endOver = inn.getTotals().getOvers().trim();
        		}
        		if (inn.getTotals().getRuns() > 0) {
        			endRuns = inn.getTotals().getRuns();
        		}
        	} 
        	
        }

        pship.setBalls(cf.getOverDifference(startOver, endOver));
        pship.setRuns(endRuns - startRuns);
        
        if (ewkt > 0) {
        	if (swkt == ewkt) {
        		pship.setWicketNo(swkt + 1);
        	} else {
        		pship.setWicketNo(ewkt);
        	}
        }
    }

}
